import asyncio
from typing import List, Dict, Any, Optional
from playwright.async_api import async_playwright, Page, Browser, BrowserContext
from loguru import logger
from datetime import datetime
import json
from pathlib import Path


class PlaywrightRecorder:
    """Playwright脚本录制器"""
    
    def __init__(self, browser_type: str = "chromium", headless: bool = False):
        self.browser_type = browser_type
        self.headless = headless
        self.browser: Optional[Browser] = None
        self.context: Optional[BrowserContext] = None
        self.page: Optional[Page] = None
        self.playwright = None
        self.actions: List[Dict[str, Any]] = []
        self.is_recording = False
        self.enable_screenshots = True
        self.screenshot_dir = Path("screenshots")
        
    async def start_recording(self, url: str = None) -> None:
        """开始录制"""
        try:
            self.playwright = await async_playwright().start()
            
            # 启动浏览器
            if self.browser_type == "chromium":
                self.browser = await self.playwright.chromium.launch(headless=self.headless)
            elif self.browser_type == "firefox":
                self.browser = await self.playwright.firefox.launch(headless=self.headless)
            elif self.browser_type == "webkit":
                self.browser = await self.playwright.webkit.launch(headless=self.headless)
            else:
                raise ValueError(f"不支持的浏览器类型: {self.browser_type}")
            
            # 创建上下文和页面
            self.context = await self.browser.new_context()
            self.page = await self.context.new_page()
            
            # 设置事件监听器
            await self._setup_event_listeners()
            
            # 如果提供了URL，则导航到该页面
            if url:
                await self.page.goto(url)
                self._add_action("navigate", {"url": url})
            
            self.is_recording = True
            logger.info(f"开始录制，浏览器类型: {self.browser_type}")
            
        except Exception as e:
            logger.error(f"启动录制失败: {e}")
            await self.cleanup()
            raise
    
    async def stop_recording(self) -> List[Dict[str, Any]]:
        """停止录制并返回动作列表"""
        try:
            self.is_recording = False
            logger.info("停止录制")
            
            # 清理资源
            await self.cleanup()
            
            return self.actions
            
        except Exception as e:
            logger.error(f"停止录制失败: {e}")
            raise
    
    async def cleanup(self) -> None:
        """清理资源"""
        try:
            if self.page:
                await self.page.close()
            if self.context:
                await self.context.close()
            if self.browser:
                await self.browser.close()
            if self.playwright:
                await self.playwright.stop()
                
        except Exception as e:
            logger.error(f"清理资源失败: {e}")
    
    async def _setup_event_listeners(self) -> None:
        """设置事件监听器"""
        if not self.page:
            return
        
        # 点击事件
        await self.page.expose_function("__recordClick", self._on_click)
        
        # 输入事件
        await self.page.expose_function("__recordInput", self._on_input)
        
        # 键盘事件
        await self.page.expose_function("__recordKeyboard", self._on_keyboard)
        
        # 页面导航事件
        self.page.on("framenavigated", self._on_navigate)
        
        # 注入监听脚本
        await self.page.add_init_script("""
            // 监听点击事件
            document.addEventListener('click', (event) => {
                if (window.__recordClick) {
                    window.__recordClick({
                        selector: getSelector(event.target),
                        text: event.target.textContent?.trim(),
                        tagName: event.target.tagName,
                        x: event.clientX,
                        y: event.clientY
                    });
                }
            });
            
            // 监听输入事件
            document.addEventListener('input', (event) => {
                if (window.__recordInput && event.target.value !== undefined) {
                    window.__recordInput({
                        selector: getSelector(event.target),
                        value: event.target.value,
                        tagName: event.target.tagName
                    });
                }
            });
            
            // 监听键盘事件
            document.addEventListener('keydown', (event) => {
                if (window.__recordKeyboard) {
                    window.__recordKeyboard({
                        key: event.key,
                        code: event.code,
                        ctrlKey: event.ctrlKey,
                        shiftKey: event.shiftKey,
                        altKey: event.altKey,
                        metaKey: event.metaKey
                    });
                }
            });
            
            // 获取元素选择器
            function getSelector(element) {
                if (element.id) {
                    return '#' + element.id;
                }
                if (element.className) {
                    return '.' + element.className.split(' ').join('.');
                }
                
                let path = [];
                while (element) {
                    let selector = element.tagName.toLowerCase();
                    if (element.id) {
                        selector += '#' + element.id;
                        path.unshift(selector);
                        break;
                    } else if (element.className) {
                        selector += '.' + element.className.split(' ').join('.');
                    }
                    
                    let sibling = element;
                    let nth = 1;
                    while (sibling = sibling.previousElementSibling) {
                        if (sibling.tagName.toLowerCase() === element.tagName.toLowerCase()) {
                            nth++;
                        }
                    }
                    if (nth > 1) {
                        selector += ':nth-of-type(' + nth + ')';
                    }
                    
                    path.unshift(selector);
                    element = element.parentElement;
                }
                
                return path.join(' > ');
            }
        """)
    
    def _on_click(self, data: Dict[str, Any]) -> None:
        """处理点击事件"""
        if self.is_recording:
            self._add_action("click", data)
    
    def _on_input(self, data: Dict[str, Any]) -> None:
        """处理输入事件"""
        if self.is_recording:
            self._add_action("input", data)
    
    def _on_keyboard(self, data: Dict[str, Any]) -> None:
        """处理键盘事件"""
        if self.is_recording:
            self._add_action("keyboard", data)
    
    def _on_navigate(self, frame) -> None:
        """处理页面导航事件"""
        if self.is_recording and frame == frame.page.main_frame:
            self._add_action("navigate", {"url": frame.url})
    
    def _add_action(self, action_type: str, data: Dict[str, Any]) -> None:
        """添加动作到记录列表"""
        action = {
            "type": action_type,
            "timestamp": datetime.now().isoformat(),
            "data": data
        }
        
        # 为重要动作添加截图
        if self.enable_screenshots and action_type in ["click", "navigate"]:
            asyncio.create_task(self._capture_screenshot(action))
        
        self.actions.append(action)
        logger.debug(f"记录动作: {action}")
    
    async def _capture_screenshot(self, action: Dict[str, Any]) -> Optional[str]:
        """捕获截图"""
        if not self.page:
            return None
        
        try:
            # 确保截图目录存在
            self.screenshot_dir.mkdir(exist_ok=True)
            
            # 生成截图文件名
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
            filename = f"{action['type']}_{timestamp}.png"
            screenshot_path = self.screenshot_dir / filename
            
            # 捕获截图
            await self.page.screenshot(path=str(screenshot_path))
            
            # 将截图路径添加到动作数据中
            action["screenshot"] = str(screenshot_path)
            
            logger.debug(f"截图已保存: {screenshot_path}")
            return str(screenshot_path)
            
        except Exception as e:
            logger.error(f"截图失败: {e}")
            return None
    
    async def take_manual_screenshot(self, description: str = "") -> Optional[str]:
        """手动截图"""
        if not self.page:
            return None
        
        try:
            screenshot_path = await self._capture_screenshot({
                "type": "manual_screenshot",
                "description": description
            })
            
            if screenshot_path:
                self._add_action("screenshot", {
                    "description": description,
                    "path": screenshot_path
                })
            
            return screenshot_path
            
        except Exception as e:
            logger.error(f"手动截图失败: {e}")
            return None
    
    def save_script(self, file_path: str, script_name: str = None) -> None:
        """保存录制的脚本"""
        try:
            script_data = {
                "name": script_name or f"录制脚本_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
                "created_at": datetime.now().isoformat(),
                "browser_type": self.browser_type,
                "actions": self.actions
            }
            
            Path(file_path).parent.mkdir(parents=True, exist_ok=True)
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(script_data, f, ensure_ascii=False, indent=2)
            
            logger.info(f"脚本已保存到: {file_path}")
            
        except Exception as e:
            logger.error(f"保存脚本失败: {e}")
            raise